/*
 * Galaxium Messenger
 * Copyright (C) 2003-2007 Philippe Durand <draekz@gmail.com>
 * Copyright (C) 2007 Ben Motmans <ben.motmans@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Text;
using System.IO;
using System.Collections.Generic;

using Galaxium.Core;

using Anculus.Core;

namespace Galaxium.Protocol
{
	public enum TransferState { Pending, Started, Finished, Aborted, Cancelled, Declined, Progressing, Failed };
	
	public abstract class AbstractFileTransfer : IFileTransfer
	{
		public event EventHandler<FileTransferEventArgs> TransferStart;
		public event EventHandler<FileTransferEventArgs> TransferFinish;
		public event EventHandler<FileTransferEventArgs> TransferLocalAbort;
		public event EventHandler<FileTransferEventArgs> TransferRemoteAbort;
		public event EventHandler<FileTransferEventArgs> TransferCancel;
		public event EventHandler<FileTransferEventArgs> TransferDecline;
		public event EventHandler<FileTransferEventArgs> TransferProgress;
		public event EventHandler<FileTransferErrorEventArgs> TransferFailed;
		
		protected string _uniqueIdentifier;
		protected TransferState _state = TransferState.Pending;
		
		protected ISession _session;
		protected IContact _contact;
		protected bool _incoming;
		
		protected Stream _stream;
		protected long _totalBytes;
		protected long _transferedBytes;
		protected long _rateBytes;
		protected string _fileName;
		protected string _destination;
		protected DateTime _startTime;
		protected bool _directTransfer = false;
		protected byte[] _preview = new byte[0];
		
		public TransferState State
		{
			get { return _state; }
		}
		
		public bool DirectTransfer
		{
			get { return _directTransfer; }
		}
		
		public string UniqueIdentifier
		{
			get { return _uniqueIdentifier; }
		}

		public ISession Session
		{
			get { return _session; }
		}

		public IContact Contact
		{
			get { return _contact; }
		}

		public bool Incoming
		{
			get { return _incoming; }
		}
		
		public Stream Stream
		{
			get { return _stream; }
			protected internal set { _stream = value; }
		}
		
		public long TotalBytes
		{
			get { return _totalBytes; }
			protected internal set { _totalBytes = value; }
		}
		
		public long TransferedBytes
		{
			get { return _transferedBytes; }
			protected internal set { _transferedBytes = value; }
		}
		
		public long RateBytes
		{
			get { return _rateBytes; }
		}
		
		public string FileName
		{
			get { return _fileName; }
		}
		
		public string Destination
		{
			get { return _destination; }
			set { _destination = value; }
		}
		
		public byte[] Preview
		{
			get { return _preview; }
		}
		
		protected AbstractFileTransfer (ISession session, IContact contact, bool incoming, string fileName)
			: this (session, contact, incoming)
		{
			ThrowUtility.ThrowIfEmpty ("fileName", fileName);
			
			this._uniqueIdentifier = Guid.NewGuid ().ToString ();
			this._fileName = fileName;
		}
		
		protected AbstractFileTransfer (string uniqueIdentifier, ISession session, IContact contact, bool incoming)
		{
			ThrowUtility.ThrowIfEmpty ("uniqueIdentifier", uniqueIdentifier);
			ThrowUtility.ThrowIfNull ("session", session);
			ThrowUtility.ThrowIfNull ("contact", contact);

			this._uniqueIdentifier = uniqueIdentifier;
			this._session = session;
			this._contact = contact;
			this._incoming = incoming;
		}

		protected AbstractFileTransfer (ISession session, IContact contact, bool incoming)
		{
			ThrowUtility.ThrowIfNull ("session", session);
			ThrowUtility.ThrowIfNull ("contact", contact);

			this._uniqueIdentifier = Guid.NewGuid ().ToString ();
			this._session = session;
			this._contact = contact;
			this._incoming = incoming;
		}
		
		public abstract void Accept ();
		public abstract void Decline ();
		public abstract void Abort ();
		
		private long GetBytePerSecond ()
		{
			TimeSpan span = DateTime.Now - _startTime;
			double bytesPerSecond = _transferedBytes / span.TotalSeconds;
			
			return (long) bytesPerSecond;
		}
		
		public string TimeLeft ()
		{
			float remainingTime = 0;
			float totalBytes = _totalBytes;
			float partialBytes = _transferedBytes;
			float bytesPerSecond = GetBytePerSecond ();
			
			StringBuilder sb = new StringBuilder ();
			
			if (bytesPerSecond != 0)
				remainingTime = (totalBytes - partialBytes) / bytesPerSecond;
			
			TimeSpan span = TimeSpan.FromSeconds (remainingTime);
			string timestring = "0s";
			
			if (span.Hours > 0)
				timestring = String.Format ("{0:00}h {1:00}m {2:00}s", span.Hours, span.Minutes, span.Seconds);
			else if (span.Minutes > 0)
				timestring = String.Format ("{0:00}m {1:00}s", span.Minutes, span.Seconds);
			else if (span.Seconds > 0)
				timestring = String.Format ("{0:00}s", span.Seconds);
			
			return timestring;
		}
		
		public string TransferRate ()
		{
			TimeSpan span = DateTime.Now - _startTime;
			
			if (span.TotalSeconds != 0)
			{
				double bytesPerSecond = _transferedBytes / span.TotalSeconds;
				return BaseUtility.SizeString (Convert.ToInt64(bytesPerSecond)) + "/s";
			}
			else
			{
				return BaseUtility.SizeString (0) + "/s";
			}
		}
		
		public virtual void Rename (string filename)
		{
			if (Stream == null)
				_fileName = filename;
			else
			{
				// We somehow need to check if we are doing an incoming, and if it hasnt started yet.
				if (Incoming)
				{
					if (TransferedBytes == 0)
					{
						Stream.Close();
						_fileName = filename;
						Stream = File.Open(filename, FileMode.Create);
					}
				}
				else
					Log.Warn ("Cannot rename outgoing file as the stream is already opened with the existing filename!");
			}
		}
		
		protected virtual void OnTransferStart (FileTransferEventArgs args)
		{
			_state = TransferState.Started;
			
			if (TransferStart != null)
				TransferStart (this, args);
		}

		protected virtual void OnTransferFinish (FileTransferEventArgs args)
		{
			_state = TransferState.Finished;
			
			if (TransferFinish != null)
				TransferFinish (this, args);
		}

		protected virtual void OnTransferLocalAbort (FileTransferEventArgs args)
		{
			_state = TransferState.Aborted;
			
			if (TransferLocalAbort != null)
				TransferLocalAbort (this, args);
		}
		
		protected virtual void OnTransferRemoteAbort (FileTransferEventArgs args)
		{
			_state = TransferState.Aborted;
			
			if (TransferRemoteAbort != null)
				TransferRemoteAbort (this, args);
		}
		
		protected virtual void OnTransferCancel (FileTransferEventArgs args)
		{
			Console.WriteLine("Setting transfer as cancelled");
			_state = TransferState.Cancelled;
			
			if (TransferCancel != null)
				TransferCancel (this, args);
		}
		
		protected virtual void OnTransferDecline (FileTransferEventArgs args)
		{
			_state = TransferState.Declined;
			
			if (TransferDecline != null)
				TransferDecline (this, args);
		}
		
		protected virtual void OnTransferProgress (FileTransferEventArgs args)
		{
			_state = TransferState.Progressing;
			
			if (TransferProgress != null)
				TransferProgress (this, args);
		}
		
		protected virtual void OnTransferFailed (FileTransferErrorEventArgs args)
		{
			_state = TransferState.Failed;
			
			if (TransferFailed != null)
				TransferFailed (this, args);
		}
	}
}